<template>
  <a-tree-select
    v-bind="getAttrs"
    @change="handleChange"
    v-model:value="innerValue"
    v-if="viewMode === 'edit'"
  >
    <template #[item]="data" v-for="item in Object.keys($slots)">
      <slot :name="item" v-bind="data || {}"></slot>
    </template>
    <template #suffixIcon v-if="loading">
      <LoadingOutlined spin />
    </template>
  </a-tree-select>
  <span v-else>{{ label }}</span>
</template>

<script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import { TreeSelect } from 'ant-design-vue';
import { isArray, isFunction, isString } from '@/utils/is';
import { cloneDeep, get } from 'lodash-es';
import { propTypes } from '@/utils/propTypes';
import { LoadingOutlined } from '@ant-design/icons-vue';
import { getTreeDict } from '@/utils/cache/busCache';
import { FormMode } from '@/enums/appEnum';
import { getData } from '@/utils/http/axios/component';
export default defineComponent({
  name: 'ApiTreeSelect',
  components: { ATreeSelect: TreeSelect, LoadingOutlined },
  props: {
    value: [Array, Object, String, Number],
    api: {
      type: [Function, String] as PropType<
        (arg?: Recordable) => Promise<Recordable>
      >,
    },
    dictCode: propTypes.string.def(''),
    params: { type: Object },
    immediate: { type: Boolean, default: true },
    resultField: propTypes.string.def(''),
    resultHandler: {
      type: Function as PropType<(arg?: Recordable) => Recordable[]>,
    },
    viewMode: {
      type: String as PropType<FormMode>,
      default: 'edit',
    },
    fieldNames: { type: Object },
    onlySelectChild: { type: Boolean, default: false },
  },
  emits: ['options-change', 'update:value', 'change'],
  setup(props, { attrs, emit }) {
    const innerValue = ref(props?.value);
    const treeData = ref<Recordable[]>([]);
    const isFirstLoaded = ref<Boolean>(false);
    const loading = ref(false);
    const label = ref('');
    const getAttrs = computed(() => {
      return {
        ...(props.api || props.dictCode ? { treeData: unref(treeData) } : {}),
        ...attrs,
      };
    });

    function handleChange(...args) {
      emit('change', ...args);
    }

    const getLabel = (value) => {
      const { fieldNames = {} } = props as Recordable;
      const labelField = fieldNames.label || 'title';
      const valueField = fieldNames.value || 'value';
      const zipTreeData = [];
      getZipData(zipTreeData, treeData.value, fieldNames, null);
      const opts = zipTreeData as Array<Recordable>;
      if (!opts || opts.length === 0) return '';
      if (value && isString(value)) {
        const findOption = opts.find((option) => option[valueField] == value);
        label.value = findOption ? findOption[labelField] : '';
        return;
      }
      if (value && isArray(value)) {
        const v = value as Array<Object>;
        const findOptions = opts.filter(
          (option) => v.indexOf(option[valueField]) > -1,
        );
        label.value =
          findOptions && findOptions.length
            ? findOptions.map((o) => o[labelField]).join(',')
            : '';
        return;
      }
      label.value = '';
    };

    watch(
      () => props.params,
      () => {
        fetch();
      },
      { deep: true, immediate: true },
    );
    watch(
      () => props.value,
      (v) => {
        innerValue.value = v;
      },
      { deep: true, immediate: true },
    );
    watch(
      () => innerValue.value,
      (v) => {
        emit('update:value', v);
        if (v) getLabel(v);
      },
      {
        immediate: true,
      },
    );
    watch(
      () => treeData.value,
      (v) => {
        if (v) getLabel(innerValue.value);
      },
      { deep: true, immediate: true },
    );
    watch(
      () => props.immediate,
      (v) => {
        v && !isFirstLoaded.value && fetch();
      },
    );
    onMounted(async () => {
      props.immediate && (await fetch());
    });
    function getZipData(n, o, fieldNames, parent) {
      const { label = 'label', children = 'children' } = fieldNames;
      o.forEach((os) => {
        os.path = parent ? os[label] + '/' + parent[label] : os[label];
        n.push(cloneDeep(os));
        if (os[children]) getZipData(n, os[children], fieldNames, os);
      });
    }
    async function fetch() {
      const { api, dictCode } = props;
      if (dictCode) {
        treeData.value = (await getTreeDict(
          dictCode,
        )) as unknown as Recordable[];
        emit('options-change', treeData.value);
        return;
      }
      if (!api || loading.value) return;
      loading.value = true;
      treeData.value = [];
      let result;
      try {
        result = await getData(api, props.params);
      } catch (e) {
        console.error(e);
      }
      loading.value = false;
      if (!result) return;
      if (!isArray(result)) {
        result = get(result, props.resultField);
      }
      if (props.resultHandler && isFunction(props.resultHandler)) {
        treeData.value = props.resultHandler(result as Recordable[]) || [];
      } else {
        treeData.value = result as Recordable[];
      }
      isFirstLoaded.value = true;
      emit('options-change', treeData.value);
    }
    return { innerValue, getAttrs, loading, label, handleChange };
  },
});
</script>
